home *** CD-ROM | disk | FTP | other *** search
/ MacHack 2000 / MacHack 2000.toast / pc / The Hacks / MacHacksBug / Python 1.5.2c1 / Tools / idle / PyShell.py < prev    next >
Encoding:
Python Source  |  2000-06-23  |  19.4 KB  |  634 lines

  1. #! /usr/bin/env python
  2.  
  3. import os
  4. import sys
  5. import string
  6. import getopt
  7. import re
  8.  
  9. import linecache
  10. from code import InteractiveInterpreter
  11.  
  12. from Tkinter import *
  13. import tkMessageBox
  14.  
  15. from EditorWindow import EditorWindow, fixwordbreaks
  16. from FileList import FileList
  17. from ColorDelegator import ColorDelegator
  18. from OutputWindow import OutputWindow
  19.  
  20. # We need to patch linecache.checkcache, because we don't want it
  21. # to throw away our <pyshell#...> entries.
  22. # Rather than repeating its code here, we save those entries,
  23. # then call the original function, and then restore the saved entries.
  24. def linecache_checkcache(orig_checkcache=linecache.checkcache):
  25.     cache = linecache.cache
  26.     save = {}
  27.     for filename in cache.keys():
  28.         if filename[:1] + filename[-1:] == '<>':
  29.             save[filename] = cache[filename]
  30.     orig_checkcache()
  31.     cache.update(save)
  32. linecache.checkcache = linecache_checkcache
  33.  
  34.  
  35. # Note: <<newline-and-indent>> event is defined in AutoIndent.py
  36.  
  37. #$ event <<plain-newline-and-indent>>
  38. #$ win <Control-j>
  39. #$ unix <Control-j>
  40.  
  41. #$ event <<beginning-of-line>>
  42. #$ win <Control-a>
  43. #$ win <Home>
  44. #$ unix <Control-a>
  45. #$ unix <Home>
  46.  
  47. #$ event <<history-next>>
  48. #$ win <Alt-n>
  49. #$ unix <Alt-n>
  50.  
  51. #$ event <<history-previous>>
  52. #$ win <Alt-p>
  53. #$ unix <Alt-p>
  54.  
  55. #$ event <<interrupt-execution>>
  56. #$ win <Control-c>
  57. #$ unix <Control-c>
  58.  
  59. #$ event <<end-of-file>>
  60. #$ win <Control-d>
  61. #$ unix <Control-d>
  62.  
  63. #$ event <<open-stack-viewer>>
  64.  
  65. #$ event <<toggle-debugger>>
  66.  
  67.  
  68. class PyShellEditorWindow(EditorWindow):
  69.  
  70.     # Regular text edit window when a shell is present
  71.     # XXX ought to merge with regular editor window
  72.  
  73.     def __init__(self, *args):
  74.         apply(EditorWindow.__init__, (self,) + args)
  75.         self.text.bind("<<set-breakpoint-here>>", self.set_breakpoint_here)
  76.         self.text.bind("<<open-python-shell>>", self.flist.open_shell)
  77.  
  78.     rmenu_specs = [
  79.         ("Set breakpoint here", "<<set-breakpoint-here>>"),
  80.     ]
  81.  
  82.     def set_breakpoint_here(self, event=None):
  83.         if not self.flist.pyshell or not self.flist.pyshell.interp.debugger:
  84.             self.text.bell()
  85.             return
  86.         self.flist.pyshell.interp.debugger.set_breakpoint_here(self)
  87.  
  88.  
  89. class PyShellFileList(FileList):
  90.  
  91.     # File list when a shell is present
  92.  
  93.     EditorWindow = PyShellEditorWindow
  94.  
  95.     pyshell = None
  96.  
  97.     def open_shell(self, event=None):
  98.         if self.pyshell:
  99.             self.pyshell.wakeup()
  100.         else:
  101.             self.pyshell = PyShell(self)
  102.             self.pyshell.begin()
  103.         return self.pyshell
  104.  
  105.  
  106. class ModifiedColorDelegator(ColorDelegator):
  107.  
  108.     # Colorizer for the shell window itself
  109.  
  110.     def recolorize_main(self):
  111.         self.tag_remove("TODO", "1.0", "iomark")
  112.         self.tag_add("SYNC", "1.0", "iomark")
  113.         ColorDelegator.recolorize_main(self)
  114.  
  115.     tagdefs = ColorDelegator.tagdefs.copy()
  116.  
  117.     tagdefs.update({
  118.         ##"stdin":   {"background": "yellow"},
  119.         "stdout":  {"foreground": "blue"},
  120.         "stderr":  {"foreground": "#007700"},
  121.         "console": {"foreground": "#770000"},
  122.         "ERROR":   {"background": "#FF7777"},
  123.         None:      {"foreground": "purple"}, # default
  124.     })
  125.  
  126.  
  127. class ModifiedInterpreter(InteractiveInterpreter):
  128.  
  129.     def __init__(self, tkconsole):
  130.         self.tkconsole = tkconsole
  131.         InteractiveInterpreter.__init__(self)
  132.  
  133.     gid = 0
  134.  
  135.     def runsource(self, source):
  136.         # Extend base class to stuff the source in the line cache
  137.         filename = "<pyshell#%d>" % self.gid
  138.         self.gid = self.gid + 1
  139.         lines = string.split(source, "\n")
  140.         linecache.cache[filename] = len(source)+1, 0, lines, filename
  141.         self.more = 0
  142.         return InteractiveInterpreter.runsource(self, source, filename)
  143.  
  144.     def showsyntaxerror(self, filename=None):
  145.         # Extend base class to color the offending position
  146.         # (instead of printing it and pointing at it with a caret)
  147.         text = self.tkconsole.text
  148.         stuff = self.unpackerror()
  149.         if not stuff:
  150.             self.tkconsole.resetoutput()
  151.             InteractiveInterpreter.showsyntaxerror(self, filename)
  152.             return
  153.         msg, lineno, offset, line = stuff
  154.         if lineno == 1:
  155.             pos = "iomark + %d chars" % (offset-1)
  156.         else:
  157.             pos = "iomark linestart + %d lines + %d chars" % (lineno-1,
  158.                                                               offset-1)
  159.         text.tag_add("ERROR", pos)
  160.         text.see(pos)
  161.         char = text.get(pos)
  162.         if char in string.letters + string.digits + "_":
  163.             text.tag_add("ERROR", pos + " wordstart", pos)
  164.         self.tkconsole.resetoutput()
  165.         self.write("SyntaxError: %s\n" % str(msg))
  166.  
  167.     def unpackerror(self):
  168.         type, value, tb = sys.exc_info()
  169.         ok = type == SyntaxError
  170.         if ok:
  171.             try:
  172.                 msg, (dummy_filename, lineno, offset, line) = value
  173.             except:
  174.                 ok = 0
  175.         if ok:
  176.             return msg, lineno, offset, line
  177.         else:
  178.             return None
  179.  
  180.     def showtraceback(self):
  181.         # Extend base class method to reset output properly
  182.         text = self.tkconsole.text
  183.         self.tkconsole.resetoutput()
  184.         self.checklinecache()
  185.         InteractiveInterpreter.showtraceback(self)
  186.  
  187.     def checklinecache(self):
  188.         c = linecache.cache
  189.         for key in c.keys():
  190.             if key[:1] + key[-1:] != "<>":
  191.                 del c[key]
  192.  
  193.     debugger = None
  194.  
  195.     def setdebugger(self, debugger):
  196.         self.debugger = debugger
  197.  
  198.     def getdebugger(self):
  199.         return self.debugger
  200.  
  201.     def runcode(self, code):
  202.         # Override base class method
  203.         debugger = self.debugger
  204.         try:
  205.             self.tkconsole.beginexecuting()
  206.             try:
  207.                 if debugger:
  208.                     debugger.run(code, self.locals)
  209.                 else:
  210.                     exec code in self.locals
  211.             except SystemExit:
  212.                 if tkMessageBox.askyesno(
  213.                     "Exit?",
  214.                     "Do you want to exit altogether?",
  215.                     default="yes",
  216.                     master=self.tkconsole.text):
  217.                     raise
  218.                 else:
  219.                     self.showtraceback()
  220.                     if self.tkconsole.getvar("<<toggle-jit-stack-viewer>>"):
  221.                         self.tkconsole.open_stack_viewer()
  222.             except:
  223.                 self.showtraceback()
  224.                 if self.tkconsole.getvar("<<toggle-jit-stack-viewer>>"):
  225.                     self.tkconsole.open_stack_viewer()
  226.  
  227.         finally:
  228.             self.tkconsole.endexecuting()
  229.  
  230.     def write(self, s):
  231.         # Override base class write
  232.         self.tkconsole.console.write(s)
  233.  
  234.  
  235. class PyShell(OutputWindow):
  236.  
  237.     # Override classes
  238.     ColorDelegator = ModifiedColorDelegator
  239.  
  240.     # Override menu bar specs
  241.     menu_specs = PyShellEditorWindow.menu_specs[:]
  242.     menu_specs.insert(len(menu_specs)-2, ("debug", "_Debug"))
  243.  
  244.     # New classes
  245.     from History import History
  246.  
  247.     def __init__(self, flist=None):
  248.         self.interp = ModifiedInterpreter(self)
  249.         if flist is None:
  250.             root = Tk()
  251.             fixwordbreaks(root)
  252.             root.withdraw()
  253.             flist = PyShellFileList(root)
  254.  
  255.         OutputWindow.__init__(self, flist, None, None)
  256.  
  257.         import __builtin__
  258.         __builtin__.quit = __builtin__.exit = "To exit, type Ctrl-D."
  259.  
  260.         self.auto = self.extensions["AutoIndent"] # Required extension
  261.         self.auto.config(prefertabs=1)
  262.  
  263.         text = self.text
  264.         text.configure(wrap="char")
  265.         text.bind("<<newline-and-indent>>", self.enter_callback)
  266.         text.bind("<<plain-newline-and-indent>>", self.linefeed_callback)
  267.         text.bind("<<interrupt-execution>>", self.cancel_callback)
  268.         text.bind("<<beginning-of-line>>", self.home_callback)
  269.         text.bind("<<end-of-file>>", self.eof_callback)
  270.         text.bind("<<open-stack-viewer>>", self.open_stack_viewer)
  271.         text.bind("<<toggle-debugger>>", self.toggle_debugger)
  272.         text.bind("<<open-python-shell>>", self.flist.open_shell)
  273.         text.bind("<<toggle-jit-stack-viewer>>", self.toggle_jit_stack_viewer)
  274.  
  275.         sys.stdout = PseudoFile(self, "stdout")
  276.         sys.stderr = PseudoFile(self, "stderr")
  277.         sys.stdin = self
  278.         self.console = PseudoFile(self, "console")
  279.  
  280.         self.history = self.History(self.text)
  281.  
  282.     reading = 0
  283.     executing = 0
  284.     canceled = 0
  285.     endoffile = 0
  286.  
  287.     def toggle_debugger(self, event=None):
  288.         if self.executing:
  289.             tkMessageBox.showerror("Don't debug now",
  290.                 "You can only toggle the debugger when idle",
  291.                 master=self.text)
  292.             self.set_debugger_indicator()
  293.             return "break"
  294.         else:
  295.             db = self.interp.getdebugger()
  296.             if db:
  297.                 self.close_debugger()
  298.             else:
  299.                 self.open_debugger()
  300.     
  301.     def set_debugger_indicator(self):
  302.         db = self.interp.getdebugger()
  303.         self.setvar("<<toggle-debugger>>", not not db)
  304.     def toggle_jit_stack_viewer( self, event=None):
  305.         pass # All we need is the variable
  306.  
  307.     def close_debugger(self):
  308.         db = self.interp.getdebugger()
  309.         if db:
  310.             self.interp.setdebugger(None)
  311.             db.close()
  312.             self.resetoutput()
  313.             self.console.write("[DEBUG OFF]\n")
  314.             sys.ps1 = ">>> "
  315.             self.showprompt()
  316.         self.set_debugger_indicator()
  317.  
  318.     def open_debugger(self):
  319.         import Debugger
  320.         self.interp.setdebugger(Debugger.Debugger(self))
  321.         sys.ps1 = "[DEBUG ON]\n>>> "
  322.         self.showprompt()
  323.         self.set_debugger_indicator()
  324.  
  325.     def beginexecuting(self):
  326.         # Helper for ModifiedInterpreter
  327.         self.resetoutput()
  328.         self.executing = 1
  329.         self._cancel_check = self.cancel_check
  330.         ##sys.settrace(self._cancel_check)
  331.  
  332.     def endexecuting(self):
  333.         # Helper for ModifiedInterpreter
  334.         sys.settrace(None)
  335.         self.executing = 0
  336.         self.canceled = 0
  337.  
  338.     def close(self):
  339.         # Extend base class method
  340.         if self.executing:
  341.             # XXX Need to ask a question here
  342.             if not tkMessageBox.askokcancel(
  343.                 "Kill?",
  344.                 "The program is still running; do you want to kill it?",
  345.                 default="ok",
  346.                 master=self.text):
  347.                 return "cancel"
  348.             self.canceled = 1
  349.             if self.reading:
  350.                 self.top.quit()
  351.             return "cancel"
  352.         reply = PyShellEditorWindow.close(self)
  353.         if reply != "cancel":
  354.             self.flist.pyshell = None
  355.             # Restore std streams
  356.             sys.stdout = sys.__stdout__
  357.             sys.stderr = sys.__stderr__
  358.             sys.stdin = sys.__stdin__
  359.             # Break cycles
  360.             self.interp = None
  361.             self.console = None
  362.         return reply
  363.  
  364.     def ispythonsource(self, filename):
  365.         # Override this so EditorWindow never removes the colorizer
  366.         return 1
  367.  
  368.     def short_title(self):
  369.         return "Python Shell"
  370.  
  371.     def begin(self):
  372.         self.resetoutput()
  373.         self.write("Python %s on %s\n%s\n" %
  374.                    (sys.version, sys.platform, sys.copyright))
  375.         try:
  376.             sys.ps1
  377.         except AttributeError:
  378.             sys.ps1 = ">>> "
  379.         self.showprompt()
  380.         import Tkinter
  381.         Tkinter._default_root = None
  382.  
  383.     def interact(self):
  384.         self.begin()
  385.         self.top.mainloop()
  386.  
  387.     def readline(self):
  388.         save = self.reading
  389.         try:
  390.             self.reading = 1
  391.             self.top.mainloop()
  392.         finally:
  393.             self.reading = save
  394.         line = self.text.get("iomark", "end-1c")
  395.         self.resetoutput()
  396.         if self.canceled:
  397.             self.canceled = 0
  398.             raise KeyboardInterrupt
  399.         if self.endoffile:
  400.             self.endoffile = 0
  401.             return ""
  402.         return line
  403.  
  404.     def cancel_callback(self, event):
  405.         try:
  406.             if self.text.compare("sel.first", "!=", "sel.last"):
  407.                 return # Active selection -- always use default binding
  408.         except:
  409.             pass
  410.         if not (self.executing or self.reading):
  411.             self.resetoutput()
  412.             self.write("KeyboardInterrupt\n")
  413.             self.showprompt()
  414.             return "break"
  415.         self.endoffile = 0
  416.         self.canceled = 1
  417.         if self.reading:
  418.             self.top.quit()
  419.         return "break"
  420.  
  421.     def eof_callback(self, event):
  422.         if self.executing and not self.reading:
  423.             return # Let the default binding (delete next char) take over
  424.         if not (self.text.compare("iomark", "==", "insert") and
  425.                 self.text.compare("insert", "==", "end-1c")):
  426.             return # Let the default binding (delete next char) take over
  427.         if not self.executing:
  428. ##             if not tkMessageBox.askokcancel(
  429. ##                 "Exit?",
  430. ##                 "Are you sure you want to exit?",
  431. ##                 default="ok", master=self.text):
  432. ##                 return "break"
  433.             self.resetoutput()
  434.             self.close()
  435.         else:
  436.             self.canceled = 0
  437.             self.endoffile = 1
  438.             self.top.quit()
  439.         return "break"
  440.  
  441.     def home_callback(self, event):
  442.         if event.state != 0 and event.keysym == "Home":
  443.             return # <Modifier-Home>; fall back to class binding
  444.         if self.text.compare("iomark", "<=", "insert") and \
  445.            self.text.compare("insert linestart", "<=", "iomark"):
  446.             self.text.mark_set("insert", "iomark")
  447.             self.text.tag_remove("sel", "1.0", "end")
  448.             self.text.see("insert")
  449.             return "break"
  450.  
  451.     def linefeed_callback(self, event):
  452.         # Insert a linefeed without entering anything (still autoindented)
  453.         if self.reading:
  454.             self.text.insert("insert", "\n")
  455.             self.text.see("insert")
  456.         else:
  457.             self.auto.auto_indent(event)
  458.         return "break"
  459.  
  460.     def enter_callback(self, event):
  461.         if self.executing and not self.reading:
  462.             return # Let the default binding (insert '\n') take over
  463.         # If some text is selected, recall the selection
  464.         # (but only if this before the I/O mark)
  465.         try:
  466.             sel = self.text.get("sel.first", "sel.last")
  467.             if sel:
  468.                 if self.text.compare("sel.last", "<=", "iomark"):
  469.                     self.recall(sel)
  470.                     return "break"
  471.         except:
  472.             pass
  473.         # If we're strictly before the line containing iomark, recall
  474.         # the current line, less a leading prompt, less leading or
  475.         # trailing whitespace
  476.         if self.text.compare("insert", "<", "iomark linestart"):
  477.             # Check if there's a relevant stdin range -- if so, use it
  478.             prev = self.text.tag_prevrange("stdin", "insert")
  479.             if prev and self.text.compare("insert", "<", prev[1]):
  480.                 self.recall(self.text.get(prev[0], prev[1]))
  481.                 return "break"
  482.             next = self.text.tag_nextrange("stdin", "insert")
  483.             if next and self.text.compare("insert lineend", ">=", next[0]):
  484.                 self.recall(self.text.get(next[0], next[1]))
  485.                 return "break"
  486.             # No stdin mark -- just get the current line
  487.             self.recall(self.text.get("insert linestart", "insert lineend"))
  488.             return "break"
  489.         # If we're in the current input before its last line,
  490.         # insert a newline right at the insert point
  491.         if self.text.compare("insert", "<", "end-1c linestart"):
  492.             self.auto.auto_indent(event)
  493.             return "break"
  494.         # We're in the last line; append a newline and submit it
  495.         self.text.mark_set("insert", "end-1c")
  496.         if self.reading:
  497.             self.text.insert("insert", "\n")
  498.             self.text.see("insert")
  499.         else:
  500.             self.auto.auto_indent(event)
  501.         self.text.tag_add("stdin", "iomark", "end-1c")
  502.         self.text.update_idletasks()
  503.         if self.reading:
  504.             self.top.quit() # Break out of recursive mainloop() in raw_input()
  505.         else:
  506.             self.runit()
  507.         return "break"
  508.  
  509.     def recall(self, s):
  510.         if self.history:
  511.             self.history.recall(s)
  512.  
  513.     def runit(self):
  514.         line = self.text.get("iomark", "end-1c")
  515.         # Strip off last newline and surrounding whitespace.
  516.         # (To allow you to hit return twice to end a statement.)
  517.         i = len(line)
  518.         while i > 0 and line[i-1] in " \t":
  519.             i = i-1
  520.         if i > 0 and line[i-1] == "\n":
  521.             i = i-1
  522.         while i > 0 and line[i-1] in " \t":
  523.             i = i-1
  524.         line = line[:i]
  525.         more = self.interp.runsource(line)
  526.         if not more:
  527.             self.showprompt()
  528.  
  529.     def cancel_check(self, frame, what, args,
  530.                      dooneevent=tkinter.dooneevent,
  531.                      dontwait=tkinter.DONT_WAIT):
  532.         # Hack -- use the debugger hooks to be able to handle events
  533.         # and interrupt execution at any time.
  534.         # This slows execution down quite a bit, so you may want to
  535.         # disable this (by not calling settrace() in runcode() above)
  536.         # for full-bore (uninterruptable) speed.
  537.         # XXX This should become a user option.
  538.         if self.canceled:
  539.             return
  540.         dooneevent(dontwait)
  541.         if self.canceled:
  542.             self.canceled = 0
  543.             raise KeyboardInterrupt
  544.         return self._cancel_check
  545.  
  546.     def open_stack_viewer(self, event=None):
  547.         try:
  548.             sys.last_traceback
  549.         except:
  550.             tkMessageBox.showerror("No stack trace",
  551.                 "There is no stack trace yet.\n"
  552.                 "(sys.last_traceback is not defined)",
  553.                 master=self.text)
  554.             return
  555.         from StackViewer import StackBrowser
  556.         sv = StackBrowser(self.root, self.flist)
  557.  
  558.     def showprompt(self):
  559.         self.resetoutput()
  560.         try:
  561.             s = str(sys.ps1)
  562.         except:
  563.             s = ""
  564.         self.console.write(s)
  565.         self.text.mark_set("insert", "end-1c")
  566.  
  567.     def resetoutput(self):
  568.         source = self.text.get("iomark", "end-1c")
  569.         if self.history:
  570.             self.history.history_store(source)
  571.         if self.text.get("end-2c") != "\n":
  572.             self.text.insert("end-1c", "\n")
  573.         self.text.mark_set("iomark", "end-1c")
  574.         sys.stdout.softspace = 0
  575.  
  576.     def write(self, s, tags=()):
  577.         self.text.mark_gravity("iomark", "right")
  578.         OutputWindow.write(self, s, tags, "iomark")
  579.         self.text.mark_gravity("iomark", "left")
  580.         if self.canceled:
  581.             self.canceled = 0
  582.             raise KeyboardInterrupt
  583.  
  584. class PseudoFile:
  585.  
  586.     def __init__(self, shell, tags):
  587.         self.shell = shell
  588.         self.tags = tags
  589.  
  590.     def write(self, s):
  591.         self.shell.write(s, self.tags)
  592.  
  593.     def writelines(self, l):
  594.         map(self.write, l)
  595.  
  596.     def flush(self):
  597.         pass
  598.  
  599.  
  600. def main():
  601.     debug = 0
  602.     try:
  603.         opts, args = getopt.getopt(sys.argv[1:], "d")
  604.     except getopt.error, msg:
  605.         sys.stderr.write("Error: %s\n" % str(msg))
  606.         sys.exit(2)
  607.     for o, a in opts:
  608.         if o == "-d":
  609.             debug = 1
  610.     global flist, root
  611.     root = Tk()
  612.     fixwordbreaks(root)
  613.     root.withdraw()
  614.     flist = PyShellFileList(root)
  615.     if args:
  616.         for filename in sys.argv[1:]:
  617.             flist.open(filename)
  618.             aPath = os.path.abspath(os.path.dirname(filename))
  619.             if not aPath in sys.path:
  620.                 sys.path.insert(0, aPath)
  621.     else:
  622.         aPath = os.getcwd()
  623.         if not aPath in sys.path:
  624.             sys.path.insert(0, aPath)
  625.     t = PyShell(flist)
  626.     flist.pyshell = t
  627.     t.begin()
  628.     if debug:
  629.         t.open_debugger()
  630.     root.mainloop()
  631.  
  632. if __name__ == "__main__":
  633.     main()
  634.